using System;
using System.Collections.Generic;
using System.Text;

namespace RapidHDL
{

    public class PipelineSerializer : Component
    {
        Dictionary<int, NodeVector> dicAddresses;

        public PipelineBus InputBus;
        public PipelineBus OutputBus;

        public StateMachine oStateMachine;

        public PipelineStage oInputStage;
        PipelineStage oOutputStage;

        ClockComponent oClock;
        NodeVector nvReset;


        NodeVector nvAddress;
        int iSequenceLength;
        int iAddressWidth;
        int iOutputWidth;

        public bool OffsetAddress = false;
        

        public PipelineSerializer(Component poParentComponent, string psName, ClockComponent poClock, NodeVector pnvReset, int piInputWidth, int piOutputDataWidth)
            : base(poParentComponent, psName)
        {
            InputBus = new PipelineBus(this, "input_bus", piInputWidth);
            oInputStage = new PipelineStage(this, "input_stage", poClock, pnvReset, piInputWidth);
            oInputStage.InputBus.Join(InputBus);
            nvReset = pnvReset;
            oClock = poClock;
            iSequenceLength = System.Convert.ToInt32(piInputWidth / (piOutputDataWidth) + .49);
            iAddressWidth = Conversion.MinBitWidth(iSequenceLength);

            oOutputStage = new PipelineStage(this, "output_stage", poClock, pnvReset, piOutputDataWidth + iAddressWidth);
            OutputBus = new PipelineBus(this, "output_bus", oOutputStage.OutputBus.Data.Width);
            OutputBus.Join(oOutputStage.OutputBus);
            iOutputWidth = piOutputDataWidth;
        }


        public override void GenerateStructure()
        {
            /* State machine*/
            oStateMachine = new StateMachine(this, "state_machine", oClock);
            oStateMachine.ResetIn.Join(nvReset);

            oStateMachine.AddState("state_reset");
            oStateMachine.SetDefaultState("state_reset");

            dicAddresses = new Dictionary<int, NodeVector>();
            
            Mux oDataMux = new Mux(this, "data_mux", iOutputWidth, iSequenceLength);

            Encoder oEncoder;
            if (!OffsetAddress)
                oEncoder = new Encoder(this, "encode", iSequenceLength, iAddressWidth);
            else
            {
                oEncoder = new Encoder(this, "encode", iSequenceLength+1, iAddressWidth);
                oEncoder.InputNodes[0, 0].Join(0);
            }

            if (!OffsetAddress)
                oDataMux.SelectNodes.Join(oEncoder.OutputNodes);
            else
            {
                Adder oAdder = new Adder(this, "dec", oDataMux.SelectNodes.Width, oDataMux.SelectNodes.Width, false);
                oAdder.InputNodesA.Join(oEncoder.OutputNodes);
                oAdder.InputNodesB.Join(Operators.CONST(this,Conversion.TwosComplement(1,oDataMux.SelectNodes.Width)));
                oDataMux.SelectNodes.Join(oAdder.OutputNodes);
            }

            int iLSB = 0;
            for (int iIdx = 0; iIdx < iSequenceLength; iIdx++)
            {
                NodeVector nvSelect = this.CreateNodeVector("addr_" + iIdx.ToString(), 1);
                
                if (!OffsetAddress)
                    nvSelect.Join(oEncoder.InputNodes[iIdx, iIdx]);
                else
                    nvSelect.Join(oEncoder.InputNodes[iIdx+1, iIdx+1]);
                
                dicAddresses.Add(iIdx, nvSelect);

                oDataMux.InputNodes[iIdx].ConnectSubset(oInputStage.OutputBus.Data, iLSB, iLSB + iOutputWidth - 1);
                iLSB += iOutputWidth;

                string sStateCurrent = "state_" + iIdx.ToString();
                oStateMachine.AddState(sStateCurrent);
                int iLastState = iIdx - 1;
                string sLastState = "state_" + iLastState.ToString();
                if (iIdx == 0)
                {
                    oStateMachine.StateTransition("state_reset", oInputStage.OutputBus.DataReady, sStateCurrent);
                }
                else
                {
                    oStateMachine.StateTransition(sLastState, !oOutputStage.InputBus.Stall, sStateCurrent);
                    if (iIdx == iSequenceLength - 1)
                        oStateMachine.StateTransition(sStateCurrent, !oOutputStage.InputBus.Stall, "state_reset");
                }
                oStateMachine.StateTransition(sStateCurrent, oOutputStage.InputBus.Stall, sStateCurrent);

                if (iIdx > 0)
                    nvSelect.Join(oStateMachine.StateCurrent(sStateCurrent));
                else
                    nvSelect.Join(oStateMachine.StateCurrent(sStateCurrent) | oStateMachine.StateCurrent("state_reset"));
            }
            oDataMux.OutputNodes.ConnectSubset(oOutputStage.InputBus.Data, 0, iOutputWidth - 1);
            oEncoder.OutputNodes.ConnectSubset(oOutputStage.InputBus.Data, iOutputWidth, iOutputWidth + iAddressWidth - 1);

            oStateMachine.StateTransition("state_reset", !oInputStage.OutputBus.DataReady, "state_reset");
            oOutputStage.InputBus.DataReady.Join(!oStateMachine.StateCurrent("state_reset"));
            int iStateLast = iSequenceLength - 1;
            string sStateLast = "state_" + iStateLast.ToString();
            NodeVector nvStallOut = Operators.AND(this, Operators.OR(this,!oStateMachine.StateCurrent(sStateLast), OutputBus.Stall), Operators.OR(this, !oStateMachine.StateCurrent("state_reset"), oOutputStage.InputBus.Stall, oStateMachine.StateCurrent("state_reset") & oInputStage.OutputBus.DataReady));
            oInputStage.OutputBus.Stall.Join(nvStallOut);
        }

    }
}
